﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ConnectionControl;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Threading;

using Usermanagment.DBConnection;
using pokerServer.GameManagment.HumanFactor;

namespace pokerServer.GameManagment.GameTools
{
	public class Player {

		public enum GameRoles { Normal, Dealer, Small, Big, DealerBig }

		private uint playerID;
		private uint playerChips;
		public String username;

		public TcpClient socket;
		public NetworkStream stream;
		public Boolean recvData;


		public int tableIndex;
		private Seat seat;
		public Table table;


		// game vars
		private uint buyIn;
		public Boolean isAllIn;
		// round vars
		public uint gameWonPot;
		public uint gameBet;
		private GameEngine.GameDecisions gameLastAction;
		private uint gameLastActionValue;
		public uint gameRoundPot; // reset to 0 every phase.
		public GameRoles gameRole;

		// variables for hand/card algorithm
		private Card PCard1;
		private Card PCard2;
		public Card[] cabestHand;
		public int[] nabestHand;
		public int HandStrength;
		// end of variable for hand/card algorithm

		public ClientConnection cc;
		protected int chips;
		protected DBConnect dbc;

		public Player(ClientConnection cc, uint playerID, String username, uint playerChips) {
			this.cc = cc;
			//this.getReady();
			this.playerID = playerID;
			this.username = username;
			this.playerChips = playerChips;

			this.init();
			this.reset();
		}
		private void init() {
			this.recvData = false;
			this.buyIn = 0;
		}
		private void reset() {
			this.isAllIn = false;
			this.gameBet = 0;
			this.gameWonPot = 0;
			this.HandStrength = 0;
			this.cabestHand = new Card[5];
			this.nabestHand = new int[5];
			this.GamesRole = GameRoles.Normal;
		}
		public void resetGame(GameRoles role) {
			this.reset();
			this.GamesRole = role;
		}

		public ClientConnection getCon() {
			return this.cc;
		}

		public uint PlayerID {
			get {
				return this.playerID;
			}
		}
		public string PlayerName {
			get {
				return this.username;
			}
		}
		public uint Chips {
			get {
				return this.playerChips + this.BuyIn;
			}
			set {
				this.playerChips = value;
			}
		}
		public GameRoles GamesRole {
			get {
				return this.gameRole;
			}
			set {
				this.gameRole = value;
			}
		}
		public virtual void setCards(Card c1, Card c2) {
			this.Card1 = c1;
			this.Card2 = c2;
		}
		public virtual Card Card1 {
			get {
				return PCard1;
			}
			set {
				PCard1 = value;
			}
		}
		public virtual Card Card2 {
			get {
				return PCard2;
			}
			set {
				PCard2 = value;
			}
		}
		public GameEngine.GameDecisions LastAction {
			get {
				return this.gameLastAction;
			}
			set {
				this.gameLastAction = value;
			}
		}
		public Card[] Cards {
			get {
				return new Card[] { this.Card1, this.Card2 };
			}
		}
		public uint LastActionValue {
			get {
				return this.gameLastActionValue;
			}
			set {
				this.gameLastActionValue = value;
			}
		}
		public Seat Seat {
			get {
				return this.seat;
			}
			set {
				this.seat = value;
			}
		}
		public Boolean isPlaying {
			get {
				GameEngine.GameDecisions gd = this.LastAction;
				return (this.isSeating) && (gd == GameEngine.GameDecisions.NoChoise || gd == GameEngine.GameDecisions.Check || gd == GameEngine.GameDecisions.Call || gd == GameEngine.GameDecisions.Raise);
			}
		}
		public Boolean isSeating {
			get {
				return (this.seat != null);
			}
		}
		public Boolean canBuyIn(uint value) {
			return !(this.playerChips < value);
		}
		public Boolean canBet(uint value) {
			return !(this.BuyIn < value);
		}
		public void SeatUP() {
			this.Seat = null;
		}
		public void SeatDown(Table t, Seat s, uint value) {
			this.table = t;
			this.BuyIn = value;
			this.Seat = s;
			//Console.WriteLine("ECHO Player.SeatDown: {0} SeatDown {1}", this, t);
		}
		public uint neededToCall(uint value) {
			return (value - this.gameRoundPot);
		}


		public uint BuyIn {
			set {
				if (!this.canBuyIn(value)) {
					throw new PlayerCannotAffordBuyInPlayerException();
				}

				this.Chips = this.playerChips - value;
				this.buyIn = value;
				//Console.WriteLine(this.Chips);
			}
			get {
				return this.buyIn;
			}
		}
		public void chgBuyIn(Int64 value) {
			this.buyIn = Convert.ToUInt32(this.BuyIn + value);
		}

		public void wonChips(uint value) {
			//this.Chips+= value;
			this.chgBuyIn(value);
			this.gameWonPot = value;
			//Console.WriteLine("ECHO Player.wonChips: {0} WonChips: {1}", this, value);
		}
		public void PlaceBet(uint value) {
			if (!this.canBet(value))
				throw new BetIsBiggerThenBuyInPlayerException();

			this.LastActionValue = value;
			this.gameRoundPot += value;
			this.table.addToPot(value);
			this.chgBuyIn(-1 * value);
			//Console.WriteLine("ECHO Player.PlaceBet: {0} PlacedBet: {1}", this, value);
		}

		public void evtNewPhase() {
			this.gameLastAction = GameEngine.GameDecisions.NoChoise; // or NoChoise
			this.gameLastActionValue = 0;
			this.gameRoundPot = 0;
		}
		/*
		public string bestHand() {
			string bestHand = "";
			foreach (Card c in this.cabestHand) {
				bestHand += c + ",";
			}
			bestHand = bestHand.Substring(0, bestHand.Length - 1);
			return bestHand;
		}
		*/

		public void playerAction(GameEngine.GameDecisions gDecision, uint value) {

			this.LastAction = gDecision;
			this.LastActionValue = value;


			//this.Player.Pl
		}



		public void sendData(string data) {
			//Console.WriteLine("sent to {0} data: {1}", this.PlayerName, data);
			//this.stream.Write(new UTF8Encoding().GetBytes(data), 0, data.Length); // to remove // at the end
		}

		public override String ToString() {
			return string.Format("Player[{0}[{1}] Seat[{3}]\tChips[{2}]\tBuyIn[{4}]\tRole[{7}]\tPlayerCards[{5}]\tHandStrength[{6}] Action[{8}] RoundPot[{9}]",
					this.username, this.PlayerID, this.Chips, this.tableIndex, this.BuyIn, this.SPlayerCards, (HandStrength > 0) ? Player.replaceHandToString(HandStrength) : "",
					this.GamesRole, this.LastAction, this.gameRoundPot);
		}

		// Setters
		public virtual string SCards {
			get {
				return "[" + PCard1 + ", " + PCard2 + "]";
			}
		}

		// Algorithms
		public virtual Card[] FullCards {
			get {
				Card[] tmp = new Card[7];
				tmp[0] = PCard1;
				tmp[1] = PCard2;
				for (int i = 0; i < table.TableCards.Length; i++) {
					tmp[i + 2] = table.TableCards[i];
				}
				return tmp;
			}
		}
		public virtual string SPlayerCards {
			get {
				if (this.Card1 == null || this.Card2 == null) {
					return null;
				}
				Card[] tmp = this.FullCards;
				string str = "";
				for (int i = 0; i < 7 && tmp[i] != null; i++) {
					str += tmp[i] + ", ";
					//if (tmp[i].getType() == 1 || tmp[i].getType() == 3) str+= "\t";
				}

				return "[" + str.Substring(0, str.Length - 2) + "]";
			}
		}
		public virtual int Hand() {
			if (HandStrength > 0) { // What is going on if i can get a better strength (also with river)
				return HandStrength;
			}

			//ORIGINAL LINE: int[][] calcHand = new int[5][14];
			//JAVA TO C# CONVERTER NOTE: The following call to the 'RectangularArrays' helper class reproduces the rectangular array initialization that is automatic in Java:
			int[][] calcHand = RectangularArrays.ReturnRectangularIntArray(5, 14);
			Card[] tmp = this.FullCards;

			// Build Matrix
			for (int i = 0; i < 7; i++) {
				Card t = tmp[i];
				if (t != null) {
					calcHand[t.Type - 1][t.Number - 2]++;
					calcHand[t.Type - 1][13]++;
					calcHand[4][t.Number - 2]++;
				}
				else {
					break;
				}
			}
			int ans = 1, max = 1;
			for (int i = 0; i < calcHand[4].Length; i++) {
				if (calcHand[4][i] > 1) {
					if (calcHand[4][i] == 2) {
						if (max == 1) {
							ans = 2;
						}
						if (max == 2) {
							ans = 3;
						}
						else if (max == 3) {
							ans += 3;
						}
						max = IntMax(max, 2);
					}
					else if (calcHand[4][i] == 3) {
						if (max == 1) {
							ans = 4;
						}
						else if (max >= 2) {
							ans = 7;
						}
						max = IntMax(max, 3);
					}
					else {
						ans = 8;
						break;
					}
				}
			}
			// End of matrix
			int c = 0, k;
			for (int i = 12; i >= 0; i--) {
				k = calcHand[4][i];
				if (k > 0) {
					nabestHand[c] = i + 2;
					c++;
					if (i == 0 && calcHand[4][12] > 0) {
						c++;
					}
				}
				else {
					c = 0;
				}
				if (c == 5) {
					if (isStraightFlush(calcHand)) {
						ans = 9;
					}
					else {
						ans = 5;
					}
					break;
				}
			}
			ans = IntMax(1, ans);
			if (ans < 6) {
				for (int i = 0; i < 4 && ans < 6; i++) {
					if (calcHand[i][13] > 4) {
						for (int j = 12, z = 0; j >= 0 && z < 5; j--) {
							if (calcHand[i][j] > 1) {
								nabestHand[z++] = i + 2;
								if (j == 0 && calcHand[i][12] > 0) {
									z++;
								}
							}
						}
						ans = 6;
					}
				}
			}
			//MyFunc.printARR(calcHand);
			// Make the best hand.
			if (ans != 5 && ans != 9) {
				bool flag = true;
				for (int i = 4, ii = 0, ssum = 0; i > 0 && flag; i--) {
					for (int j = 12; j >= 0 && flag; j--) {
						if (calcHand[4][j] == i) {
							for (int z = 0; z < i && flag; z++, ii++) {
								nabestHand[ii] = j + 2;
								if (ii == 4) {
									flag = false;
								}
							}
							ssum = ((ssum + i) <= 5) ? ssum + i : 5;
						}
					}
				}
			}

			HandStrength = ans;
			this.HandStrength = ans;
			return ans;
		}
		private bool isStraightFlush(int[][] cHands) {
			int c;
			for (int i = 0; i < 4; i++) {
				c = 0;
				for (int j = 0; j < 13; j++) {
					if (cHands[i][j] > 0) {
						c++;
						if (j == 0 && cHands[i][12] > 0) {
							c++;
						}
					}
					else {
						c = 0;
					}
					if (c == 5) {
						return true;
					}
				}
			}
			return false;
		}

		public virtual string getSHand(Card[] cards) {
			return replaceHandToString(this.Hand());
		}
		public virtual int CompareHands(Card[] cards, Player p1, Player p2) {
			return p1.CompareHands(p2);
		}
		public virtual int CompareHands(Player p) {
			int ans = 0, h1 = this.Hand(), h2 = p.Hand();
			if (h1 == h2) {
				ans = this.CompareSimilarHands(p.nabestHand);
			}
			else {
				ans = (h1 > h2) ? 1 : 2;
			}
			return ans;
		}
		public virtual int CompareSimilarHands(int[] c2) {
			int ans = 0, cn1, cn2;
			for (int i = 0; i < 5 && nabestHand[i] > 0; i++) {
				cn1 = nabestHand[i];
				cn2 = c2[i];
				if (cn1 == cn2) {
					continue;
				}
				else {
					ans = (cn1 > cn2) ? 1 : 2;
					break;
				}
			}
			return ans;
		}
		public virtual void getBestHand(Card[] cards) {
			for (int i = 0; i < nabestHand.Length; i++) {
				Console.Write(nabestHand[i] + "\t");
			}
			Console.WriteLine();
		}


		/*
		public TcpClient Socket {
			get {
				return socket;
			}
			set {
				this.socket = value;
				this.stream = this.socket.GetStream();
			}
		}


		public void recveiveData() {
			string data;
			StreamReader sr = new StreamReader(stream);
			while (socket.Connected) {
				if (socket.Available > 0) {
					data = sr.ReadLine();
					//recvHandler.proccess(this, data);
				}
				Thread.Sleep(100);
			}
		}
		*/
		public int IntMax(int a, int b) {
			return (a > b) ? a : b;
		}

		public static string replaceHandToString(int ans) {
			string ans2 = "";
			switch (ans) {
				case 1:
					ans2 = "HighCards";
					break;
				case 2:
					ans2 = "OnePair";
					break;
				case 3:
					ans2 = "TwoPair";
					break;
				case 4:
					ans2 = "ThreeOfAKind";
					break;
				case 5:
					ans2 = "Straight";
					break;
				case 6:
					ans2 = "Flush";
					break;
				case 7:
					ans2 = "FullHouse";
					break;
				case 8:
					ans2 = "FourOfAKind";
					break;
				case 9:
					ans2 = "StraightFlush";
					break;
				default:
					break;
			}
			return ans2;
		}
	}


	// Player Exceptions
	public abstract class PlayerException : Exception { }
	public class PlayerIsNullPlayerException : PlayerException { }
	public class PlayerCannotAffordBuyInPlayerException : PlayerException { }
	public class BetIsBiggerThenBuyInPlayerException : PlayerException { }

}
